/**
 * 
 */
package es.deusto.herotactics.database;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

import com.herotactics.fuentedatos.FuenteDatos;

import jugador.Jugador;
import logica.Partida;
import logica.Turno;
import mapa.Coordenada;
import mapa.Mapa;

import organizacion.Equipo;

import terrenos.Casilla;
import unidades.Unidad;
import unidades.UnidadHT;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import es.deusto.herotactics.utils.Game;

/**
 * Clase que gestiona la base de datos SQLite para el sistema operativo Android
 * 
 * @author Ibai
 * 
 */
public class FuenteDatosSQLite implements FuenteDatos {

	/**
	 * Atributo de clase que contiene la conexion a la BD. Es necesario hacer un init() para crearla.
	 */
	private SQLiteDatabase db = null;
	/**
	 * Constante que contiene el nombre del archivo de base de datos (sin la extension)
	 */
	private static final String NOMBRE_BD = "HeroTactics";
	/**
	 * Constante que contiene la ruta a la base de datos SQLite.
	 */
	private static final String RUTA_BD = Game.pathAHT+"DB/"+NOMBRE_BD+".s3db";
	/**
	 * Con el identificativo de la tupla de la tabla Equipo borra dicha tupla de la base de datos.
	 *
	 * @param idEquipo
	 * @return el numero de tuplas modificadas en la base de datos.
	 * @throws Exception
	 */
	private int deleteEquipo(int idEquipo) throws Exception {
		int retorno = db.delete(
				"EQUIPO",
				"ID_EQUIPO = ?",
				new String[]{String.valueOf(idEquipo)});
		return retorno;
	}

	/* (non-Javadoc)
	 * @see com.herotactics.fuentedatos.FuenteDatos#deleteJugador(jugador.Jugador)
	 */
	public int deleteJugador(Jugador arg0) throws Exception {
		int retorno = db.delete(
				"JUGADOR",
				"NOMBRE_JUGADOR = ?",
				new String[]{arg0.getNombre()});
		return retorno;
	}

	/* (non-Javadoc)
	 * @see com.herotactics.fuentedatos.FuenteDatos#deletePartida(logica.Partida)
	 */
	public int deletePartida(Partida arg0) throws Exception {
		int retorno = db.delete(
				"PARTIDA",
				"FECHA = ?",
				new String[]{String.valueOf(arg0.getFecha())});
		return retorno;
	}

	/* (non-Javadoc)
	 * @see com.herotactics.fuentedatos.FuenteDatos#deleteUnidad(unidades.UnidadHT)
	 */
	public int deleteUnidad(Unidad arg0) throws Exception {
		UnidadHT arg = (UnidadHT) arg0;
		int retorno = db.delete(
				"UNIDAD",
				"ID_UNIDAD = ?",
				new String[]{String.valueOf(arg.getId())});
		return retorno;
	}

	/* (non-Javadoc)
	 * @see com.herotactics.fuentedatos.FuenteDatos#end()
	 */
	public void end() throws Exception {
		db.close();
	}

	/**
	 * Metodo que devuelve una partida recibiendo como parametro una fecha.
	 * @param fecha
	 * @return Partida con dicha fecha.
	 * @throws Exception
	 */
	public Partida getPartida(long fecha) throws Exception {
		Cursor c = db.rawQuery("SELECT * FROM PARTIDA WHERE FECHA = ?", new String[]{String.valueOf(fecha)});
		c.moveToFirst();
		int idEquipoA = c.getInt(c.getColumnIndex("EQUIPOA"));
		int idEquipoB = c.getInt(c.getColumnIndex("EQUIPOB"));
		Jugador jugadorA = getJugador(idEquipoA);
		Jugador jugadorB = getJugador(idEquipoB);
		Turno turno = getTurno(c.getInt(c.getColumnIndex("TURNO")), c.getInt(c.getColumnIndex("PUNTOS_ACCION")));
		Mapa mapa = getMapa(c.getInt(c.getColumnIndex("ID_MAPA")));
//		Unidad[][] unidades = mapa.getUnidades();
//		for (int i=0; i<unidades.length; i++) {
//			for (int j=0; j<unidades[0].length; j++) {
//				unidades[i][j] = null;
//			}
//		}
//		Collection<Unidad> uds = jugadorA.getEquipo().getUnidades();
//		for (Unidad u : uds) {
//			Coordenada coord = u.getPosicion();
//			unidades[coord.getX()][coord.getY()] = u;
//		}
//		uds = jugadorB.getEquipo().getUnidades();
//		for (Unidad u : uds) {
//			Coordenada coord = u.getPosicion();
//			unidades[coord.getX()][coord.getY()] = u;
//		}
//		mapa.setUnidades(unidades);
		Partida partida = new Partida(mapa, turno, jugadorA, jugadorB, fecha);
		partida.setPosicionado();
		partida.getTurno().setPartida(partida);
		c.close();
		return partida;
	}
	
//	/**
//	 * Metodo que rellena el array bidimensional de unidades recibido como parametro con todas las unidades de los equipos recibidos como parametros.
//	 * @param idEquipoA
//	 * @param idEquipoB
//	 * @param unidades
//	 * @throws Exception
//	 */
//	public void getUnidades(int idEquipoA, int idEquipoB, Unidad[][] unidades) throws Exception {
//		Cursor c = db.rawQuery("SELECT * FROM EQUIPO_HAS_UNIDAD WHERE ID_EQUIPO IN (?, ?)",
//				new String[]{String.valueOf(idEquipoA), String.valueOf(idEquipoB)});
//		while(c.moveToNext()) {
//			UnidadHT unidad = getUnidad(c.getInt(c.getColumnIndex("ID_UNIDAD")));
//			Coordenada coord = new Coordenada(c.getInt(c.getColumnIndex("POS_X")), c.getInt(c.getColumnIndex("POS_Y")));
//			unidad.setPosicion(coord);
//			unidad.setPuedeAtacar(c.getInt(c.getColumnIndex("PUEDE_ATACAR"))>0);
//			unidad.setPuedeMover(c.getInt(c.getColumnIndex("PUEDE_MOVER"))>0);
//			unidad.setPuedeRegenerar(c.getInt(c.getColumnIndex("PUEDE_REGENERAR"))>0);
//			unidad.setVidaPartida(c.getInt(c.getColumnIndex("VIDA_PARTIDA")));
//			unidades[coord.getX()][coord.getY()] = unidad;
//		}
//		
//	}

	/* (non-Javadoc)
	 * @see com.herotactics.fuentedatos.FuenteDatos#getAllPartida()
	 */
	public Collection<Partida> getAllPartida() throws Exception {
		Collection<Partida> collection = new ArrayList<Partida>();
		Cursor c = db.rawQuery("SELECT FECHA FROM PARTIDA", null);
		while (c.moveToNext()) {
			Partida p = getPartida(c.getLong(c.getColumnIndex("FECHA")));
			p.setPosicionado();
			collection.add(p);
		}
		c.close();
		return collection;

	}

	/**
	 * Metodo que recibe la id de un equipo y devuelve el equipo correspondiente a esa id en la base de datos.
	 * @param idEquipo
	 * @return objeto Equipo
	 * @throws Exception
	 */
	private Equipo getEquipo(int idEquipo) throws Exception {
		ArrayList<Unidad> unidades = new ArrayList<Unidad>();
		Cursor c = db.rawQuery("SELECT * FROM EQUIPO_HAS_UNIDAD WHERE ID_EQUIPO =?", new String[]{String.valueOf(idEquipo)});
		while (c.moveToNext()) {
			UnidadHT unidad = getUnidad(c.getInt(c.getColumnIndex("ID_UNIDAD")));
			unidad.setVidaPartida(c.getInt(c.getColumnIndex("VIDA_PARTIDA")));
			unidad.setPosicion(new Coordenada(c.getInt(c.getColumnIndex("POS_X")), c.getInt(c.getColumnIndex("POS_Y"))));
			unidad.setPuedeAtacar((c.getInt(c.getColumnIndex("PUEDE_ATACAR")))>0); 	//no existe getBoolean o equivalente
			unidad.setPuedeMover((c.getInt(c.getColumnIndex("PUEDE_MOVER")))>0);  	//no existe getBoolean o equivalente
			unidades.add(unidad);
		}
		c.close();
		return new Equipo(unidades);

	}

	/**
	 * Metodo que recibe el id de un equipo y devuelve el objeto jugador asociado a ese equipo en la base de datos.
	 * @param idEquipo
	 * @return objeto Jugador.
	 * @throws Exception
	 */
	public Jugador getJugador(int idEquipo) throws Exception {
		String nomJugador = null;
		int creditos = 0;
		Cursor c1 = db.rawQuery("SELECT NOMBRE_JUGADOR FROM EQUIPO WHERE ID_EQUIPO = ?;", new String[]{String.valueOf(idEquipo)});
		while (c1.moveToNext()) {
			nomJugador = c1.getString(c1.getColumnIndex("NOMBRE_JUGADOR"));
		}
		Cursor c2 = db.rawQuery("SELECT CREDITOS FROM JUGADOR WHERE NOMBRE_JUGADOR = ?;", new String[]{nomJugador});
		while (c2.moveToNext()) {
			creditos = c2.getInt(c2.getColumnIndex("CREDITOS"));
		}
		Jugador jugador = new Jugador(nomJugador, creditos);
		jugador.setEquipo(getEquipo(idEquipo));
		c1.close();
		c2.close();
		return jugador;
	}

	/* (non-Javadoc)
	 * @see com.herotactics.fuentedatos.FuenteDatos#getAllMapa()
	 */
	public Collection<Mapa> getAllMapa() throws IOException, ClassNotFoundException {//FUNCIONA
		File[] files = new File("").listFiles();
		Collection<Mapa> collection = new ArrayList<Mapa>();
		for (File f : files) {
			FileInputStream fMapa = new FileInputStream(f);
			ObjectInputStream mapa = new ObjectInputStream(fMapa);
			collection.add((Mapa) mapa.readObject());
			mapa.close();
			fMapa.close();
		}
		return collection;
	}


	/* (non-Javadoc)
	 * @see com.herotactics.fuentedatos.FuenteDatos#getMapa(int)
	 */
	public Mapa getMapa(int idMapa) throws IllegalArgumentException {
		Collection<String> mapas = Mapa.getMapas();
		for(String s : mapas) {
			Mapa mapa = Mapa.getMapa(s);
			if(mapa.getIdMapa() == idMapa) {
				if (mapa.getUnidades() == null) {
					Casilla[][] c = mapa.getTerreno();
					mapa.setUnidades(new Unidad[c.length][c[0].length]);
				}
				return mapa;
			}
		}
		throw new IllegalArgumentException("El mapa no existe");
	}

	/**
	 * @param idEquipo
	 * @param puntosAccion
	 * @return Devuelve el objeto Turno con la idEquipo que se le a pasado por
	 *         parametro y actualiza los puntos de acciï¿œn con el segundo
	 *         parametro.
	 * @throws Exception
	 */
	private Turno getTurno(int idEquipo, int puntosAccion) throws Exception {
		return new Turno(getJugador(idEquipo), null, puntosAccion);
	}

	/**
	 * @param idUnidad
	 * @return Devuelve el objeto UnidadHT con la idUnidad que se le a pasado por parametro.
	 * @throws Exception
	 */
	public UnidadHT getUnidad(int idUnidad) throws Exception {//FUNCIONA
		String path = null;
		Cursor c = db.rawQuery("SELECT PATH_UNIDAD FROM UNIDAD WHERE ID_UNIDAD =?", new String[]{String.valueOf(idUnidad)});
		while (c.moveToNext()) {
			int columnIndex = c.getColumnIndex("PATH_UNIDAD");
			path = c.getString(columnIndex);
		}
		c.close();
		Class<?> clase = Class.forName(path);
		Object objeto = clase.newInstance();
		if (objeto instanceof UnidadHT) {
			return (UnidadHT) objeto;
		} else {
			return null;
		}
	}

	/* (non-Javadoc)
	 * @see com.herotactics.fuentedatos.FuenteDatos#init()
	 */
	public void init() throws Exception {
		db = SQLiteDatabase.openOrCreateDatabase(RUTA_BD, null);
		db.execSQL("CREATE TABLE IF NOT EXISTS JUGADOR(NOMBRE_JUGADOR VARCHAR2 (40) PRIMARY KEY NOT NULL,CREDITOS INTEGER);");
		db.execSQL("CREATE TABLE IF NOT EXISTS UNIDAD(ID_UNIDAD  INTEGER   PRIMARY KEY NOT NULL,PATH_UNIDAD VARCHAR2 (60));");
		db.execSQL("CREATE TABLE IF NOT EXISTS EQUIPO(ID_EQUIPO  INTEGER   PRIMARY KEY NOT NULL,NOMBRE_JUGADOR VARCHAR2 (40)  REFERENCES JUGADOR    ON DELETE CASCADE);");
		db.execSQL("CREATE TABLE IF NOT EXISTS PARTIDA(FECHA   VARCHAR (19)  PRIMARY KEY NOT NULL,EQUIPOA  INTEGER   REFERENCES EQUIPO(ID_EQUIPO)    ON DELETE CASCADE,EQUIPOB  INTEGER   REFERENCES EQUIPO(ID_EQUIPO)    ON DELETE CASCADE,TURNO   INTEGER   REFERENCES EQUIPO(ID_EQUIPO) ON DELETE CASCADE,ID_MAPA     INTEGER,PUNTOS_ACCION INTEGER);");
		db.execSQL("CREATE TABLE IF NOT EXISTS EQUIPO_HAS_UNIDAD(ID_EQUIPO INTEGER  REFERENCES EQUIPO ON DELETE CASCADE, ID_UNIDAD  INTEGER REFERENCES UNIDAD ON DELETE CASCADE,VIDA_PARTIDA INTEGER    NOT NULL,POS_X     INTEGER    NOT NULL,    POS_Y INTEGER NOT NULL,PUEDE_ATACAR BOOLEAN NOT NULL,PUEDE_MOVER BOOLEAN NOT NULL,PUEDE_REGENERAR BOOLEAN NOT NULL,PRIMARY KEY(ID_EQUIPO,ID_UNIDAD,POS_X,POS_Y));");
		
	}

	/** Metodo que inserta un equipo en la base de datos.
	 * @param arg0
	 * @param jugador
	 * @return el id del equipo
	 * @throws Exception
	 */
	private synchronized int insertEquipo(Equipo arg0, String jugador) throws Exception {

		{
			ContentValues cv = new ContentValues();
			cv.put("NOMBRE_JUGADOR", jugador);
			db.insert("EQUIPO", null, cv);
		}

		Cursor c = db.rawQuery("SELECT MAX(ID_EQUIPO) FROM EQUIPO", null); //duda: el "as _id"
		c.moveToFirst();

		int idEquipo = c.getInt(0);
		insertUnidad(arg0.getUnidades());
		insertEquipoHasUnidad(arg0.getUnidades(), idEquipo);
		c.close();
		return idEquipo;

	}

	/**
	 * Llamado por insertEquipo inserta todas las unidades dependiendo del equipo en la tabla EQUIPO_HAS_UNIDAD
	 * @param arg0
	 * @param idEquipo
	 * @return tuplas modificadas
	 * @throws Exception
	 */
	private int insertEquipoHasUnidad(Collection<Unidad> arg0, int idEquipo) throws Exception {
		int tuplasModificadas = 0;
		Iterator<Unidad> iterator = arg0.iterator();
		while (iterator.hasNext()) {
			UnidadHT unidad = (UnidadHT) iterator.next();

			ContentValues cv = new ContentValues();
			cv.put("ID_EQUIPO", idEquipo);
			cv.put("ID_UNIDAD", unidad.getId());
			cv.put("VIDA_PARTIDA", unidad.getVidaPartida());
			cv.put("POS_X", unidad.getPosicion().getX());
			cv.put("POS_Y", unidad.getPosicion().getY());
			cv.put("PUEDE_ATACAR", unidad.puedeAtacar());
			cv.put("PUEDE_MOVER", unidad.puedeMover());
			cv.put("PUEDE_REGENERAR", unidad.puedeRegenerar());
			tuplasModificadas += (int)(db.insert("EQUIPO_HAS_UNIDAD", null, cv));

		}
		return tuplasModificadas;



	}

	/* (non-Javadoc)
	 * @see com.herotactics.fuentedatos.FuenteDatos#insertJugador(jugador.Jugador)
	 */
	public int insertJugador(Jugador arg0) throws Exception {

		ContentValues cv = new ContentValues();
		cv.put("NOMBRE_JUGADOR", arg0.getNombre());
		cv.put("CREDITOS", arg0.getCreditos());
		return (int)(db.insert("JUGADOR", null, cv));

	}

	/* (non-Javadoc)
	 * @see com.herotactics.fuentedatos.FuenteDatos#insertPartida(logica.Partida)
	 */
	public int insertPartida(Partida arg0) throws Exception {
		int equipoA = insertEquipo(arg0.getJugadorA().getEquipo(), arg0.getJugadorA().getNombre());
		int equipoB = insertEquipo(arg0.getJugadorB().getEquipo(), arg0.getJugadorB().getNombre());
		int turno = 0;
		updateJugador(arg0.getJugadorA());
		updateJugador(arg0.getJugadorB());
		if (arg0.getTurno().getJugadorActivo().getNombre().equals(arg0.getJugadorA().getNombre())) {
			turno = equipoA;
		} else {
			turno = equipoB;
		}

		ContentValues cv = new ContentValues();
		cv.put("FECHA", System.currentTimeMillis());
		cv.put("EQUIPOA", equipoA);
		cv.put("EQUIPOB", equipoB);
		cv.put("TURNO", turno);
		cv.put("ID_MAPA", arg0.getMapa().getIdMapa());
		cv.put("PUNTOS_ACCION", arg0.getTurno().getPuntosAccion());

		return (int)(db.insert("PARTIDA", null, cv));

	}

	/**
	 * @param arg0
	 * @return el numero de tuplas insertadas en la tabla Unidad.
	 * @throws Exception
	 */
	public int insertUnidad(UnidadHT arg0) throws Exception {

		ContentValues cv = new ContentValues();
		cv.put("ID_UNIDAD", arg0.getId());
		cv.put("PATH_UNIDAD", arg0.getClass().getName());
		return (int)(db.insert("UNIDAD", null, cv));

	}


	/* (non-Javadoc)
	 * @see com.herotactics.fuentedatos.FuenteDatos#insertUnidad(java.util.Collection)
	 */
	public int insertUnidad(Collection<Unidad> arg0) throws Exception {
		int total = 0;
		for (Unidad u : arg0) {
			total += insertUnidad((UnidadHT)u);
		}
		return total;
	}

	/* (non-Javadoc)
	 * @see com.herotactics.fuentedatos.FuenteDatos#updateJugador(jugador.Jugador)
	 */
	public int updateJugador(Jugador jugador) throws Exception { //FUNCIONA
		ContentValues cv = new ContentValues();
		cv.put("CREDITOS", jugador.getCreditos());
		int retorno = db.update("JUGADOR", cv, "NOMBRE_JUGADOR = ?", new String[]{jugador.getNombre()});
		return retorno;
	}

	@Override
	public Collection<String> getAllJugadores() throws SQLException {
		Collection<String> collection = new ArrayList<String>();
		Cursor c = db.rawQuery("SELECT NOMBRE_JUGADOR FROM JUGADOR", null);
		while (c.moveToNext()) {
			collection.add(c.getString(c.getColumnIndex("NOMBRE_JUGADOR")));
		}
		c.close();
        return collection;
	}
}